#include "FMDBApplication.h"
#include "DBDiagnostic.h"
#include "oci.h"
#include "OCIConstDefine.h"
#include <assert.h>



FMDBApplication::FMDBApplication(void)
{
	m_pConString=NULL;

	//OCI variables
	m_pEnv=(OCIEnv *)NULL;//oci environment handle
	m_pErr=NULL;		//oci error handle
	m_pAuth=NULL;	//oci Authentication handle
	m_pSpool=NULL;			//oci session pool

	//session pool
	m_pPoolName=NULL;	//session pool name
	m_PoolNameLen=0;						//length of session pool name 
	m_min_pool=CONNECTION_POOLSIZE_MIN;						//minimal sessions in session pool
	m_max_pool=CONNECTION_POOLSIZE_MAX;					//maximum sessions in session pool
	m_increment_pool=CONNECTION_POOLSIZE_INCREMENTAL;				//the number to increment session pool by

	//authentication
	m_pUserName=NULL;
	m_pPassword=NULL;

	//statement
	m_stmt_CacheSize=DEFAULT_STATEMENT_CACHE_SIZE;

	//others
	m_bIsDBAvailable=false;
}


FMDBApplication::~FMDBApplication(void)
{
	//release OCI resource
	CloseDB();

	free(m_pConString);
	free(m_pUserName);
	free(m_pPassword);
}

static FMDBApplication o_mOracleDB;

FMDBApplication& FMDBApplication::GetInstance()
{
	return o_mOracleDB;
}

void FMDBApplication::OpenDB()
{
	//close database before open
	//note: need to check out if close operation needed in CloseDB function
	CloseDB();

	//note:we can use "OCIEnvNlsCreate" to create OCI environment with different character set.
	//     but I suggest we use database character set to simplified the database checking
	CheckEnv(m_pEnv,OCIEnvCreate(&m_pEnv,					
		OCI_THREADED,			//multi-thread mode
		NULL,NULL,NULL,NULL,	//call backs,context
		(size_t)0,				//extra memory size
		(void **)NULL));		//returned extra memory

	//get error handle
	CheckEnv(m_pEnv,OCIHandleAlloc(m_pEnv,
		(void **)&m_pErr,
		OCI_HTYPE_ERROR,
		(size_t)0,
		(void **)NULL));

	//get authentication handle
	CheckEnv(m_pEnv,OCIHandleAlloc(m_pEnv,
		(void **)&m_pAuth,
		OCI_HTYPE_AUTHINFO,
		(size_t)0,
		(void **)NULL));


	//set user name
	CheckErr(m_pErr,OCIAttrSet(m_pAuth,OCI_HTYPE_AUTHINFO,(void*)m_pUserName,(ub4)strlen(m_pUserName)*sizeof(char),OCI_ATTR_USERNAME,m_pErr));
	//set user password
	CheckErr(m_pErr,OCIAttrSet(m_pAuth,OCI_HTYPE_AUTHINFO,(void*)m_pPassword,(ub4)strlen(m_pPassword)*sizeof(char),OCI_ATTR_PASSWORD,m_pErr));


	//get session pool
	//note: session pool only need be create once in one application for threads
	CheckEnv(m_pEnv,OCIHandleAlloc(m_pEnv,(void **)&m_pSpool,OCI_HTYPE_SPOOL,(size_t)0,(void **)NULL));
	//set statement size for session pool
	//note: this is setting for all session, if each session has its own setting ,then do not need this.
	CheckErr(m_pErr,OCIAttrSet(m_pSpool,OCI_HTYPE_SPOOL,&m_stmt_CacheSize,0,OCI_ATTR_SPOOL_STMTCACHESIZE,m_pErr));
	CheckErr(m_pErr,OCISessionPoolCreate(m_pEnv,m_pErr,
		m_pSpool,
		(text **)&m_pPoolName,&m_PoolNameLen,
		(const text*)m_pConString,(ub4)strlen(m_pConString)* sizeof(char),
		m_min_pool,m_max_pool,m_increment_pool,
		(text *)"",0,(text *)"",0,
		OCI_SPC_STMTCACHE));//we use only one user to connect DB

	m_bIsDBAvailable=true;//open db successfully
}

void FMDBApplication::CloseDB()
{
	//note:it seems that the OCI variables can be close before be allocated. need to make sure, otherwise uncommented next "if" statement
	if (m_bIsDBAvailable==false)
	{
	return;
	}
	
	if (m_pEnv)
	{
		try
		{
			//close global variables
			OCIHandleFree(m_pAuth,OCI_HTYPE_AUTHINFO);	//free authentication handle
			OCIHandleFree(m_pErr,OCI_HTYPE_ERROR);	//free error handle
			OCIHandleFree(m_pEnv,OCI_HTYPE_ENV);	//free environment handle
		}
		catch (...)
		{
			assert(FALSE);//,_T("error when release OCI environment handle"));
		}
		m_pAuth=nullptr;
		m_pErr=nullptr;
		m_pEnv=nullptr;
	}
	
	
	

	m_bIsDBAvailable=false;
}

//create new session from session pool
OCISvcCtx *FMDBApplication::CreateSession(sb4 iStatementCache/*=-1*/ )
{
	return CreateSession(NULL,NULL,0,iStatementCache);
}

OCISvcCtx * FMDBApplication::CreateSession(char AttriValue[][64],ub4 *AttriCode,short AttriNumber )
{
	return CreateSession(AttriValue,AttriCode,AttriNumber,-1);
}

OCISvcCtx * FMDBApplication::CreateSession(char AttriValue[][64],ub4 *AttriCode,short AttriNumber,sb4 iStatementCache )
{
	OCISvcCtx *pSvcCtx=NULL;	//oci service context handle

	//get connection
	//note:caller should call ReleaseSession() to release resource of the session and return session to session pool
	CheckErr(m_pErr,OCISessionGet(m_pEnv,m_pErr,
		&pSvcCtx,									//returned connection
		m_pAuth,										//authentication for this connection
		(text *)m_pPoolName,(ub4)m_PoolNameLen,//session pool name
		NULL,0,NULL,NULL,NULL,OCI_SESSGET_SPOOL));

	//set session statement cache size
	if(iStatementCache>=0)//use session pool attribute
	{
		size_t stmt_CacheSize=iStatementCache;//note: this size must be investigated and confirm,and it override session pool's attribute
		CheckErr(m_pErr,OCIAttrSet(pSvcCtx,OCI_HTYPE_SVCCTX,&stmt_CacheSize,0,OCI_ATTR_STMTCACHESIZE,m_pErr));
	}

	//set diagnostic attribute for logging
	for (int i =0;i<AttriNumber;i++)
	{
		DBDiagnostic::SetSessionDiagnosticAttribute(pSvcCtx,m_pErr,&AttriValue[i][0],AttriCode[i]);
	}
	

	return pSvcCtx;
}

//release a session and return it to session pool
void FMDBApplication::ReleaseSession( OCISvcCtx *pSession )
{
	if (pSession)
	{
		CheckErr(m_pErr,OCISessionRelease(pSession,m_pErr,NULL,0,OCI_DEFAULT));		
		pSession=NULL;
	}

}

OCIError * FMDBApplication::GetOCIErrorHandle()
{
	OCIError *pErr=NULL;

	//get error handle,we suggest one connection uses one error handle
	CheckEnv(m_pEnv,OCIHandleAlloc(m_pEnv,
		(void **)&pErr,
		OCI_HTYPE_ERROR,
		(size_t)0,
		(void **)NULL));
	return pErr;
}



